/*
 * Copyright (c) 2018-2023 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "xdrbase.h"
#include "channel.h"
#include "api.h"

static int fd = -1;

/**
 * @brief       API: get job ID list from the cluster.
 *
 * @param[in]   jobreq       #1: query job request key values
 * @param[in]   master       #2: master(port and name) to be called
 *
 * @retval      not NULL     #1: succeed
 * @retval      NULL         #2: failed
 *
 * @note        When succeed, QuickPoolJobReply should be freed by the caller.
 *              For JS replied error, caller should handle it.
 *              This is the first step of query job information from JS.
 */
struct QuickPoolJobReply *
client_get_jobids(struct request *jobreq, struct poolMaster *master)
{
    if (NULL == jobreq || 0 == jobreq->number) {
        log_message(ERR, NOCLASS, "%s: Must specify job query request.", __func__);
	return NULL;
    }

    struct auth auth;
    gf_set_auth(&auth, commandParams[AUTH_KIND].string_value);

    int size; // for calculating the size of the request.
    size = gf_xdrsize_request(jobreq);
    size += gf_xdrsize_auth(&auth);
    size += HEADER_LEN;

    char *request;
    request = (char *)calloc(size, sizeof(char));
    if (NULL == request) {

        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc", size);
        gf_xdr_free(gf_xdr_auth, (void *)&auth);
        quickpool_errno = ERROR_MEMORY;
        return NULL;
    }

    struct header tohdr, outhdr;
    gf_init_header(&tohdr);
    tohdr.reqcode = BATCH_JOB_INFO;

    XDR xdrs;
    xdrmem_create(&xdrs, request, size, XDR_ENCODE);

    if (!gf_xdr_message(&xdrs, (char *)jobreq , &tohdr, &auth, gf_xdr_request, NULL)) {

        // fail to encode request.
        log_message(ERR, NOCLASS, STR_FUNC_FAIL, __func__, "gf_xdr_message");
        xdr_destroy(&xdrs);
        FREEUP(request);
        gf_xdr_free(gf_xdr_auth, (void *)&auth);

        return NULL;
    }

    char *masterList;
    unsigned int port;
    unsigned int connTimeout;
    unsigned int recvTimeout;
    if (NULL != master) { // client set master, let's call it directly.
        masterList = master->master;
        port = master->port;
        connTimeout = master->conntimeout;
        recvTimeout = master->recvtimeout;
    } else {
        masterList = qp_getmastername();
        if (NULL == masterList) {
            // JM do not know who is master, let's try g_masterlist.
            masterList = g_masterlist;
        }
        port = g_js_port;
        connTimeout = g_conntimeout;
        recvTimeout = g_recvtimeout;
    }

    char *reply;
    int cc;
    cc = gf_callmaster(masterList, port, connTimeout, recvTimeout, g_connretries,
                       XDR_GETPOS(&xdrs), request, &outhdr, &reply, &fd);

    xdr_destroy(&xdrs);
    FREEUP(request);
    gf_xdr_free(gf_xdr_auth, (void *)&auth);

    if (0 > cc) {

        if (0 < fd) {
            gf_chan_close(fd);
        }

        FREEUP(reply);
        log_message(ERR, NOCLASS, STR_FUNC_S_FAIL, __func__, "gf_callmaster", masterList);

        return NULL;
    }
    
    quickpool_errno = outhdr.reqcode;

    struct QuickPoolJobReply *jobReply = NULL;
    jobReply = (struct QuickPoolJobReply *)calloc(1, sizeof(struct QuickPoolJobReply));
    if (NULL == jobReply) {

        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__,
                    "calloc", sizeof(struct QuickPoolJobReply));
        FREEUP(reply);
        gf_chan_close(fd);

        return NULL;
    }

    // got job reply from JS, decode it.
    xdrmem_create(&xdrs, reply, outhdr.length, XDR_DECODE);
    if (!libxdr_job_reply(&xdrs, jobReply, &outhdr, NULL)) {

        log_message(ERR, NOCLASS, STR_FUNC_FAIL, __func__, "libxdr_job_reply");
        xdr_destroy(&xdrs);
        FREEUP(jobReply);
        FREEUP(reply);
        gf_chan_close(fd);

        return NULL;
    }

    xdr_destroy(&xdrs);
    FREEUP(reply);

    return (jobReply);
} // end function client_get_jobids

/**
 * @brief       API: receive job information from JS and decode it.
 *
 * @retval      not NULL     #1: succeed
 * @retval      NULL         #2: failed
 *
 * @note        When succeed, QuickPoolJobInfo should be freed by the caller.
 *              connetction with JS is closed if error occurs.
 *              This is the second step of query job information from JS.
 */
struct QuickPoolJobInfo *
client_get_jobinfo(void)
{
    struct header outhdr;
    char *reply = NULL;
    int ret;
    ret = gf_read_reply(fd, g_recvtimeout, &outhdr, &reply);
    if (0 > ret) { // failed to receive more job information.

        gf_chan_close(fd);
        quickpool_errno = ERROR_EOF;
        return NULL;
    }

    struct QuickPoolJobInfo *jobInfo;
    jobInfo = (struct QuickPoolJobInfo *)calloc(1, sizeof(struct QuickPoolJobInfo));
    if (NULL == jobInfo) {

        gf_chan_close(fd);
        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__,
                    "calloc", sizeof(struct QuickPoolJobInfo));
        return NULL;
    }

    XDR xdrs;
    xdrmem_create(&xdrs, reply, outhdr.length, XDR_DECODE);

    unsigned int numIndicies = 0; // numIndicies is used inside the xdr function.
    if (!libxdr_jobinfo(&xdrs, jobInfo, &outhdr, &numIndicies)) {

        gf_chan_close(fd);
        log_message(ERR, NOCLASS, STR_FUNC_FAIL, __func__, "libxdr_jobinfo");
        xdr_destroy(&xdrs);
        FREEUP(jobInfo);
        FREEUP(reply);

        return NULL;
    }

    xdr_destroy(&xdrs);
    FREEUP(reply);

    return jobInfo;
} // end function client_get_jobinfo

/**
 * @brief       API: close socket connection after getting jobs from JS.
 *
 * @note        This is the last step of query job information from JS.
 */
void
client_finish_getting()
{
    gf_chan_close(fd);
} // end function client_finish_getting

/**
 * @brief       API: force a job running on specific nodes.
 *
 * @param[in]   runjobreq    #1: force running job request key values
 * @param[in]   master       #2: master(port and name) to be called
 *
 * @retval      > 0          #1: succeed, new position of the job
 * @retval      -1           #2: failed
 *
 * @note        When failed, error message is printed out in this API.
 */
int
client_runjob(struct request *runjobreq, struct poolMaster *master)
{
    if (NULL == runjobreq || 0 == runjobreq->number) {
        log_message(ERR, NOCLASS, "%s: Must specify force run job request.", __func__);
	return -1;
    }

    // check the first key to avoid unknown request from client.
    if (REQUEST_KEY_OPTIONS != runjobreq->keyvalues[0].key) {
        log_message(ERR, NOCLASS, "%s: First key must be option for force run job request.",
                    __func__);
	return -1;
    }

    struct auth auth;
    gf_set_auth(&auth, commandParams[AUTH_KIND].string_value);

    unsigned int size; // for calculating the size of the request.
    size = gf_xdrsize_request(runjobreq);
    size += gf_xdrsize_auth(&auth);
    size += HEADER_LEN;

    char *request;
    request = (char *)calloc(size, sizeof(char));
    if (NULL == request) {

        log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc", size);
        gf_xdr_free(gf_xdr_auth, (void *)&auth);
        quickpool_errno = ERROR_MEMORY;
	return -1;
    }

    XDR xdrs;
    xdrmem_create(&xdrs, request, size, XDR_ENCODE);

    struct header tohdr, outhdr;
    gf_init_header(&tohdr);
    tohdr.reqcode = BATCH_JOB_FORCE;

    if (!gf_xdr_message(&xdrs, runjobreq, &tohdr, &auth, gf_xdr_request, NULL)) {

        // fail to encode request.
        log_message(ERR, NOCLASS, STR_FUNC_FAIL, __func__, "gf_xdr_message");
        xdr_destroy(&xdrs);
        FREEUP(request);
        gf_xdr_free(gf_xdr_auth, (void *)&auth);
        quickpool_errno = ERROR_XDR;

        return -1;
    }

    char *masterList;
    unsigned int port;
    unsigned int connTimeout;
    unsigned int recvTimeout;
    if (NULL != master) { // client set master, let's call it directly.
        masterList = master->master;
        port = master->port;
        connTimeout = master->conntimeout;
        recvTimeout = master->recvtimeout;
    } else {
        masterList = qp_getmastername();
        if (NULL == masterList) {
             // JM do not know who is master, let's try g_masterlist.
            masterList = g_masterlist;
        }

        port = g_js_port;
        connTimeout = g_conntimeout;
        recvTimeout = g_recvtimeout;
    }

    char *reply;
    int cc, fd;
    cc = gf_callmaster(masterList, port, connTimeout, recvTimeout, g_connretries,
                       XDR_GETPOS(&xdrs), request, &outhdr, &reply, &fd);

    if (0 < fd) {
        gf_chan_close(fd);
    }
    FREEUP(request);
    xdr_destroy(&xdrs);
    gf_xdr_free(gf_xdr_auth, (void *)&auth);

    if (0 > cc) { // communication in trouble.

        FREEUP(reply);
        log_message(ERR, NOCLASS, STR_FUNC_S_FAIL, __func__, "gf_callmaster", masterList);
        return -1;
    }

    quickpool_errno = outhdr.reqcode;
    if (quickpool_errno != 0) {

        // JS replied error message, decode it.
        xdrmem_create(&xdrs, reply, outhdr.length, XDR_DECODE);

        char *strErrMsg=NULL;
        if (!gf_xdr_string(&xdrs, &strErrMsg, &outhdr, NULL)) {

            log_message(ERR, NOCLASS, STR_FUNC_FAIL, __func__, "gf_xdr_string");
            xdr_destroy(&xdrs);
            FREEUP(reply);

            return -1;
        }

        if (NULL != strErrMsg) {
            log_message(ERR, NOCLASS, "%s", strErrMsg);
            FREEUP(strErrMsg);
        } else  {
            log_message(ERR, NOCLASS, "%s: unknown error occurs.", __func__);
        }

        xdr_destroy(&xdrs);
        FREEUP(reply);

        return -1;
    }

    FREEUP(reply);

    return(0);
} // end function client_runjob

/**
 * @brief       duplicate job submit request.
 *
 * @param[in]   subreq       #1: copy submit request from
 * @param[out]  tosubreq     #2: copy submit request to
 * @param[in]   jobid        #3: set job ID for change the job file
 *
 */
void
copy_submit_request(struct submitRequest *subreq, struct submitRequest *tosubreq, JOBID_T jobid)
{
    memcpy((char *)tosubreq, (char *)subreq, sizeof(struct submitRequest));

    if (jobid) {

        char fn[PATH_MAX+1];
        char strJobId[32];
        if (NULL == subreq->script || '\0' == subreq->script[0]) {
            snprintf(fn, PATH_MAX, "%ld.%s", time(NULL), jobid_2longstring(jobid, strJobId));
        } else {

            if (subreq->options & SUBOPT_RESTART) {
                snprintf(fn, PATH_MAX, "%s.%s", subreq->script, jobid_2string(jobid, strJobId));
            } else {
                snprintf(fn, PATH_MAX, "%s", subreq->script);
            }                    
        }

        tosubreq->script = copy_string(fn);
    } else {
        tosubreq->script = copy_string(subreq->script);
    }

    tosubreq->submit_time = subreq->submit_time;
    tosubreq->cwd = copy_string(subreq->cwd);
    tosubreq->from_node  = copy_string(subreq->from_node);
    tosubreq->home_dir = copy_string(subreq->home_dir);
    tosubreq->command = copy_string(subreq->command);
    tosubreq->queue = copy_string(subreq->queue);

    if (NULL != subreq->resource_requirement) {
        tosubreq->resource_requirement = copy_string(subreq->resource_requirement);
    }

    if (NULL != subreq->dependency) {
        tosubreq->dependency = copy_string(subreq->dependency);
    }

    if (NULL != subreq->prejob_command) {
        tosubreq->prejob_command = copy_string(subreq->prejob_command);
    }

    if (NULL != subreq->postjob_command) {
        tosubreq->postjob_command = copy_string(subreq->postjob_command);
    }        

    if (NULL != subreq->execute_host_type) {
        tosubreq->execute_host_type = copy_string(subreq->execute_host_type);
    }

    if (NULL != subreq->login_shell) {
        tosubreq->login_shell = copy_string(subreq->login_shell);
    } else {
        tosubreq->login_shell = copy_string(EMPTY_STRING);
    }

    if (NULL != subreq->mail_user)
        tosubreq->mail_user = copy_string(subreq->mail_user);
    else
        tosubreq->mail_user = copy_string(EMPTY_STRING);

    if (NULL != subreq->project) {
        tosubreq->project = copy_string(subreq->project);
    }

    if (NULL != subreq->job_name) {
        tosubreq->job_name = copy_string(subreq->job_name);
    }

    if (NULL != subreq->input_file) {
        tosubreq->input_file = copy_string(subreq->input_file);
    } else {
        tosubreq->input_file = copy_string(EMPTY_STRING);
    }

    if (NULL != subreq->output_file) {
        tosubreq->output_file = copy_string(subreq->output_file);
    } else {
        tosubreq->output_file = copy_string(EMPTY_STRING);
    }

    if (NULL != subreq->error_file) {
        tosubreq->error_file = copy_string(subreq->error_file);
    } else {
        tosubreq->error_file = copy_string(EMPTY_STRING);
    }

    if (0 < subreq->num_exec_files) {
        tosubreq->exec_files = (struct execution_file *)calloc(subreq->num_exec_files,
                                                               sizeof(struct execution_file));
        if (NULL == tosubreq->exec_files) {
            log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc",
                        subreq->num_exec_files*sizeof(struct execution_file));
            return;
        }

        memcpy(tosubreq->exec_files, subreq->exec_files,
               subreq->num_exec_files*sizeof(struct execution_file));
    } else {
        tosubreq->num_exec_files = 0;
        tosubreq->exec_files = NULL;
    }

    dup_asked_hosts(tosubreq, subreq);

    if (NULL != subreq->user_group) {
        tosubreq->user_group = copy_string(subreq->user_group);
    }

    if (NULL != subreq->job_group) {
        tosubreq->job_group = copy_string(subreq->job_group);
    }

    tosubreq->uid = subreq->uid;
    if (NULL != subreq->user) {
        tosubreq->user = copy_string(subreq->user);
    } else {
        tosubreq->user = copy_string(EMPTY_STRING);
    }

    tosubreq->chkpnt_duration = subreq->chkpnt_duration;
    if (NULL != subreq->chkpnt_dir) {
        tosubreq->chkpnt_dir = copy_string(subreq->chkpnt_dir);
    } else {
        tosubreq->chkpnt_dir = copy_string(EMPTY_STRING);
    }

    if (NULL != subreq->chkpnt_method) {
        tosubreq->chkpnt_method = copy_string(subreq->chkpnt_method);
    } else {
        tosubreq->chkpnt_method = copy_string(EMPTY_STRING);
    }

    tosubreq->num_gpu = subreq->num_gpu;
    tosubreq->gpu_exclusive = subreq->gpu_exclusive;
    tosubreq->gpu_mem = subreq->gpu_mem;
    if (NULL != subreq->gpu_model) {
        tosubreq->gpu_model = copy_string(subreq->gpu_model);
    } else {
        tosubreq->gpu_model = copy_string(EMPTY_STRING);
    }

    if (NULL != subreq->gpu_mps) {
        tosubreq->gpu_mps = copy_string(subreq->gpu_mps);
    } else {
        tosubreq->gpu_mps = copy_string(EMPTY_STRING);
    }

    if (NULL != subreq->requeue_exitcode) {
        tosubreq->requeue_exitcode = copy_string(subreq->requeue_exitcode);
    }

    return;
} // end function copy_submit_request

/**
 * @brief       duplicate job specified node.
 *
 * @param[out]  to           #1: copy submit request to
 * @param[in]   from         #2: copy submit request from
 *
 */
void
dup_asked_hosts(struct submitRequest *to , struct submitRequest *from)
{

    if (0 < from->num_spec_node) {

        to->spec_nodes = (char **)calloc(from->num_spec_node, sizeof(char *));
        if (NULL == to->spec_nodes) {
            log_message(ERR, NOCLASS, STR_FUNC_D_FAIL_M, __func__, "calloc",
                        from->num_spec_node*sizeof(char *));
            return;
        }

        unsigned int i;
        for (i=0; i<from->num_spec_node; i++) {
            to->spec_nodes[i] = copy_string(from->spec_nodes[i]);
        }

        to->num_spec_node = from->num_spec_node;
    } else {
        to->num_spec_node = 0;
        to->spec_nodes = NULL;
    }

    return;
} // end function dup_asked_hosts
